PLATFORM ?= fvp_armv8r

NAME := baremetal
OPT_LEVEL = 2
DEBUG_LEVEL = 3

ifneq ($(MAKECMDGOALS), clean)
ifeq ($(PLATFORM),)
$(error Undefined platform)
endif
endif

# Set O variable if not already done on the command line;
# or avoid confusing packages that can use the O=<dir> syntax for out-of-tree
# build by preventing it from being forwarded to sub-make calls.
ifneq ("$(origin O)", "command line")
O := $(CURDIR)/build
endif

# Remove the trailing '/.' from $(O) as it can be added by the makefile wrapper
# installed in the $(O) directory.
# Also remove the trailing '/' the user can set when on the command line.
override O := $(patsubst %/,%,$(patsubst %.,%,$(O)))
# Make sure $(O) actually exists before calling realpath on it; this is to
# avoid empty OUTPUT_O in case on non-existing entry.
OUTPUT_O := $(shell mkdir -p $(O) >/dev/null 2>&1)$(realpath $(O))

# gcc fails to build when the srcdir contains a '@'
ifneq ($(findstring @,$(OUTPUT_O)),)
$(error The build directory can not contain a '@')
endif
$(if $(OUTPUT_O),, $(error output directory "$(O)" does not exist))

CUR_DIR:= $(abspath .)
SRC_DIR:= $(CUR_DIR)/src
BUILD_DIR:=$(OUTPUT_O)/$(PLATFORM)
TARGET:=$(BUILD_DIR)/$(NAME)
CORE_DIR:=$(SRC_DIR)/core
PLATFORM_DIR:=$(SRC_DIR)/platform/$(PLATFORM)
DRIVERS_DIR:=$(SRC_DIR)/drivers
SRC_DIRS:= $(CUR_DIR) $(SRC_DIR) $(CORE_DIR) $(PLATFORM_DIR)
INC_DIRS+=$(addsuffix /inc, $(SRC_DIRS))

ifeq ($(wildcard $(PLATFORM_DIR)),)
$(error unsupported platform $(PLATFORM))
endif

-include $(SRC_DIR)/sources.mk
C_SRC+=$(addprefix $(SRC_DIR)/, $(src_c_srcs))

-include $(CORE_DIR)/sources.mk
C_SRC+=$(addprefix $(CORE_DIR)/, $(core_c_srcs))

-include $(PLATFORM_DIR)/plat.mk
-include $(PLATFORM_DIR)/sources.mk
C_SRC+=$(addprefix $(PLATFORM_DIR)/, $(plat_c_srcs))
ASM_SRC+=$(addprefix $(PLATFORM_DIR)/, $(plat_s_srcs))

SRC_DIRS+= $(foreach driver, $(drivers), $(DRIVERS_DIR)/$(driver))
INC_DIRS+= $(foreach driver, $(drivers), $(DRIVERS_DIR)/$(driver)/inc)
-include $(foreach driver, $(drivers), $(DRIVERS_DIR)/$(driver)/sources.mk)
C_SRC+= $(addprefix $(DRIVERS_DIR)/, $(driver_c_srcs))
ASM_SRC+= $(addprefix $(DRIVERS_DIR)/, $(driver_s_srcs))

ARCH_DIR:= $(SRC_DIR)/arch/$(ARCH)
SRC_DIRS+= $(ARCH_DIR)
INC_DIRS+= $(ARCH_DIR)/inc
-include $(ARCH_DIR)/arch.mk
-include $(ARCH_DIR)/sources.mk
C_SRC+=$(addprefix $(ARCH_DIR)/, $(arch_c_srcs))
ASM_SRC+=$(addprefix $(ARCH_DIR)/, $(arch_s_srcs))

-include $(CUR_DIR)/tests/build_tests.mk

LD_FILE:= $(ARCH_DIR)/linker.ld
GEN_LD_FILE:= $(BUILD_DIR)/linker.ld
OBJS = $(C_SRC:$(CUR_DIR)/%.c=$(BUILD_DIR)/%.o) $(ASM_SRC:$(CUR_DIR)/%.S=$(BUILD_DIR)/%.o) $(T_OBJS)
DEPS = $(OBJS:%=%.d) $(GEN_LD_FILE).d
DIRS:=$(sort $(dir $(OBJS) $(DEPS)))

CC=$(CROSS_COMPILE)gcc
AS=$(CROSS_COMPILE)as
LD=$(CROSS_COMPILE)ld
OBJCOPY=$(CROSS_COMPILE)objcopy
OBJDUMP=$(CROSS_COMPILE)objdump

GENERIC_FLAGS = $(ARCH_GENERIC_FLAGS) -O$(OPT_LEVEL) -g$(DEBUG_LEVEL) \
                -ffreestanding -Wframe-larger-than=16384 -fomit-frame-pointer

ASFLAGS = $(GENERIC_FLAGS) $(ARCH_ASFLAGS) -mgeneral-regs-only -D__ASSEMBLY_
CFLAGS = $(GENERIC_FLAGS) $(ARCH_CFLAGS) 
CPPFLAGS =	$(ARCH_CPPFLAGS) $(addprefix -I, $(INC_DIRS)) -MD -MF $@.d

LDFLAGS = $(GENERIC_FLAGS) $(ARCH_LDFLAGS) -nostartfiles -Wl,--build-id=none
all: $(TARGET).bin

ifneq ($(MAKECMDGOALS), clean)
-include $(DEPS)
endif

$(TARGET).bin: $(TARGET).elf
	$(OBJCOPY) -O binary $< $@

$(TARGET).elf: $(OBJS) $(GEN_LD_FILE)
	$(CC) $(LDFLAGS) -T$(GEN_LD_FILE) $(OBJS) -o $@
	$(OBJDUMP) -x -d --wide $@ > $(TARGET).asm

$(BUILD_DIR):
	mkdir -p $@

$(OBJS): | $(BUILD_DIR)

$(BUILD_DIR)/%.o: $(CUR_DIR)/%.c
	$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@

$(BUILD_DIR)/%.o: $(CUR_DIR)/%.S
	$(CC) $(ASFLAGS) $(CPPFLAGS) -c $< -o $@

$(GEN_LD_FILE): $(LD_FILE)
	$(CC) $(CPPFLAGS) -E -x assembler-with-cpp $< | grep "^[^#;]" > $@

.SECONDEXPANSION:

$(OBJS) $(DEPS): | $$(@D)/

$(DIRS):
	mkdir -p $@


clean:
	@rm -rf $(O)
	@rm -f *.elf
	@rm -f *.bin	
	@rm -f *.asm
	@rm -f *.lst

.DEFAULT_GOAL := all

.PHONY: all clean

